Activity: common recipes
We will work through common scenarios in a provided codebase:
- Generating boilerplate code (e.g., CRUD operations).
- Writing tests.
- Automating repetitive tasks (e.g., batch renaming variables) and documentation.
Generating boilerplate code
In this section, we will quickly create a Web API for an e-commerce application. All we will provide are the names of the entities and hope that Copilot will generate the rest.
Suggested prompts:
- Create a Web API for an e-commerce application with the following entities:
Product
,Category
,Order
,Customer
,Payment
. Suggest reasonable field names and types for each entity. Use Flask and SQLAlchemy.
Here's some possible outcome:
- JavaScript
- Python
- C#/.NET
const express = require('express');
const sequelize = require('./models');
const { Product } = require('./models/relationships');
const app = express();
app.use(express.json());
// Basic CRUD routes for Product
app.get('/products', async (req, res) => {
try {
const products = await Product.findAll();
res.json(products);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.post('/products', async (req, res) => {
try {
const product = await Product.create(req.body);
res.status(201).json(product);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
const PORT = process.env.PORT || 3000;
async function startServer() {
try {
await sequelize.sync();
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
} catch (error) {
console.error('Unable to start server:', error);
}
}
startServer();
And you should also get instructions on how to set things up like:
-
Install required packages
npm init -y
npm install express sequelize -
Create the required folder structure
mkdir models
-
Copy the code files into their respective locations
-
Run the server:
node server.js
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///ecommerce.db'
db = SQLAlchemy(app)
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
products = db.relationship('Product', backref='category', lazy=True)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text)
price = db.Column(db.Float, nullable=False)
stock = db.Column(db.Integer, nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
class Customer(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
name = db.Column(db.String(100), nullable=False)
address = db.Column(db.String(200))
phone = db.Column(db.String(20))
orders = db.relationship('Order', backref='customer', lazy=True)
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), nullable=False)
order_date = db.Column(db.DateTime, default=datetime.utcnow)
status = db.Column(db.String(50), default='pending')
total_amount = db.Column(db.Float, nullable=False)
payment = db.relationship('Payment', backref='order', uselist=False)
class Payment(db.Model):
id = db.Column(db.Integer, primary_key=True)
order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False)
amount = db.Column(db.Float, nullable=False)
payment_date = db.Column(db.DateTime, default=datetime.utcnow)
payment_method = db.Column(db.String(50), nullable=False)
status = db.Column(db.String(50), default='pending')
# Basic CRUD routes for Product
@app.route('/products', methods=['GET'])
def get_products():
products = Product.query.all()
return jsonify([{
'id': p.id,
'name': p.name,
'price': p.price,
'stock': p.stock,
'category_id': p.category_id
} for p in products])
@app.route('/products', methods=['POST'])
def create_product():
data = request.get_json()
product = Product(
name=data['name'],
description=data.get('description'),
price=data['price'],
stock=data['stock'],
category_id=data['category_id']
)
db.session.add(product)
db.session.commit()
return jsonify({'id': product.id, 'message': 'Product created successfully'}), 201
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
using Microsoft.EntityFrameworkCore;
using ECommerceApi.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<ECommerceContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure middleware
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
To set up and run this project:
-
Create a new WebAPI project:
dotnet new webapi -n ECommerceApi
-
Install required packages:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools -
Add connection string to appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ECommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true"
} -
Create and apply migrations:
dotnet ef migrations add InitialCreate
dotnet ef database update -
Run the application:
dotnet run
Writing tests
A huge win with Copilot is that it can generate a lot of tests quickly. However, you should always review the tests and make sure they are correct. Copilot is not perfect and it can generate tests that are not correct or that do not cover all the edge cases.
- JavaScript
- Python
- C#/.NET
Open the file where you store the server code and use the Chat and ask it to generate tests for the API. You can use the following prompt:
Generate tests for this file
The more context you provide, the better the results. You could specify the the type of test, the framework you want to use, etc.
// Mock the models
jest.mock('./models/relationships');
jest.mock('./models');
describe('Server', () => {
let app;
beforeEach(() => {
// Clear all mocks before each test
jest.clearAllMocks();
// Reset the express app
app = express();
app.use(express.json());
// Import fresh instance of routes
require('./server');
});
describe('GET /products', () => {
it('should return all products', async () => {
const mockProducts = [
{ id: 1, name: 'Product 1' },
{ id: 2, name: 'Product 2' }
];
Product.findAll.mockResolvedValue(mockProducts);
const response = await request(app)
.get('/products');
expect(response.status).toBe(200);
expect(response.body).toEqual(mockProducts);
expect(Product.findAll).toHaveBeenCalled();
});
it('should handle errors', async () => {
Product.findAll.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.get('/products');
expect(response.status).toBe(500);
expect(response.body).toHaveProperty('error');
});
});
describe('POST /products', () => {
it('should create a new product', async () => {
const newProduct = { name: 'New Product' };
const createdProduct = { id: 1, ...newProduct };
Product.create.mockResolvedValue(createdProduct);
const response = await request(app)
.post('/products')
.send(newProduct);
expect(response.status).toBe(201);
expect(response.body).toEqual(createdProduct);
expect(Product.create).toHaveBeenCalledWith(newProduct);
});
it('should handle validation errors', async () => {
const invalidProduct = { name: '' }; // Assuming name is required
const response = await request(app)
.post('/products')
.send(invalidProduct);
expect(response.status).toBe(400);
expect(response.body).toHaveProperty('error', 'Validation error');
})
it('should handle database errors', async () => {
const newProduct = { name: 'New Product' };
Product.create.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.post('/products')
.send(newProduct);
expect(response.status).toBe(500);
expect(response.body).toHaveProperty('error', 'Database error');
})
})
});
Open the file where you store the server code and use the Chat and ask it to generate tests for the API. You can use the following prompt:
Generate tests for this file
The more context you provide, the better the results. You could specify the the type of test, the framework you want to use, etc.
import pytest
from boilerplate import app, db, Product, Category
import json
@pytest.fixture
def client():
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['TESTING'] = True
with app.test_client() as client:
with app.app_context():
db.create_all()
yield client
db.session.remove()
db.drop_all()
@pytest.fixture
def sample_category(client):
category = Category(name='Electronics', description='Electronic items')
db.session.add(category)
db.session.commit()
return category
def test_get_empty_products(client):
response = client.get('/products')
assert response.status_code == 200
assert json.loads(response.data) == []
def test_get_products(client, sample_category):
product = Product(
name='Test Product',
description='Test Description',
price=99.99,
stock=10,
category_id=sample_category.id
)
db.session.add(product)
db.session.commit()
response = client.get('/products')
data = json.loads(response.data)
assert response.status_code == 200
assert len(data) == 1
assert data[0]['name'] == 'Test Product'
assert data[0]['price'] == 99.99
assert data[0]['stock'] == 10
assert data[0]['category_id'] == sample_category.id
def test_create_product_success(client, sample_category):
product_data = {
'name': 'New Product',
'description': 'New Description',
'price': 199.99,
'stock': 50,
'category_id': sample_category.id
}
response = client.post('/products',
data=json.dumps(product_data),
content_type='application/json')
assert response.status_code == 201
data = json.loads(response.data)
assert 'id' in data
assert data['message'] == 'Product created successfully'
def test_create_product_missing_fields(client):
product_data = {
'name': 'New Product',
'price': 199.99
}
response = client.post('/products',
data=json.dumps(product_data),
content_type='application/json')
assert response.status_code == 500
def test_create_product_invalid_category(client):
product_data = {
'name': 'New Product',
'description': 'New Description',
'price': 199.99,
'stock': 50,
'category_id': 999 # Non-existent category
}
response = client.post('/products',
data=json.dumps(product_data),
content_type='application/json')
assert response.status_code == 500
Open the file where you store the server code and use the Chat and ask it to generate tests for the API. You can use the following prompt:
The more context you provide, the better the results. You could specify the the type of test, the framework you want to use, etc.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moq;
using Xunit;
using ECommerceApi.Controllers;
using ECommerceApi.Data;
using ECommerceApi.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
namespace ECommerceApi.Tests
{
public class ProductsControllerTests
{
private readonly Mock<ECommerceContext> _mockContext;
private readonly ProductsController _controller;
private readonly Mock<DbSet<Product>> _mockSet;
public ProductsControllerTests()
{
_mockContext = new Mock<ECommerceContext>();
_mockSet = new Mock<DbSet<Product>>();
_controller = new ProductsController(_mockContext.Object);
}
[Fact]
public async Task GetProducts_ReturnsProductList_WhenProductsExist()
{
// Arrange
var products = new List<Product>
{
new Product { Id = 1, Name = "Test Product 1", Price = 10.99m },
new Product { Id = 2, Name = "Test Product 2", Price = 20.99m }
}.AsQueryable();
_mockSet.As<IQueryable<Product>>().Setup(m => m.Provider).Returns(products.Provider);
_mockSet.As<IQueryable<Product>>().Setup(m => m.Expression).Returns(products.Expression);
_mockSet.As<IQueryable<Product>>().Setup(m => m.ElementType).Returns(products.ElementType);
_mockSet.As<IQueryable<Product>>().Setup(m => m.GetEnumerator()).Returns(products.GetEnumerator());
_mockContext.Setup(c => c.Products).Returns(_mockSet.Object);
// Act
var result = await _controller.GetProducts();
// Assert
var actionResult = Assert.IsType<ActionResult<IEnumerable<Product>>>(result);
var returnValue = Assert.IsAssignableFrom<IEnumerable<Product>>(actionResult.Value);
Assert.Equal(2, returnValue.Count());
}
[Fact]
public async Task GetProducts_ReturnsEmptyList_WhenNoProductsExist()
{
// Arrange
var products = new List<Product>().AsQueryable();
_mockSet.As<IQueryable<Product>>().Setup(m => m.Provider).Returns(products.Provider);
_mockSet.As<IQueryable<Product>>().Setup(m => m.Expression).Returns(products.Expression);
_mockSet.As<IQueryable<Product>>().Setup(m => m.ElementType).Returns(products.ElementType);
_mockSet.As<IQueryable<Product>>().Setup(m => m.GetEnumerator()).Returns(products.GetEnumerator());
_mockContext.Setup(c => c.Products).Returns(_mockSet.Object);
// Act
var result = await _controller.GetProducts();
// Assert
var actionResult = Assert.IsType<ActionResult<IEnumerable<Product>>>(result);
var returnValue = Assert.IsAssignableFrom<IEnumerable<Product>>(actionResult.Value);
Assert.Empty(returnValue);
}
}
}
Automating repetitive tasks
There are some common tasks that you will need to do over and over again. For example, renaming variables, changing the structure of a file, etc. Your IDE can help with renaming variables, but Copilot can help with more complex tasks like adding new fields to a class, changing the structure of a file, etc. Let's try that:
Have think about the result, what would you change? You're dealing with ORMs, do we need a migration? Do we need to update the database? Do we need to update the API? As you can see, there might be a lot of things to do. Copilot can help with that, but you need to be careful and review the changes.
- JavaScript
- Python
- C#/.NET
Open the file that contains a model, type the following prompt and let Copilot do the rest:
Add fields for create date and updated date and any restrictions you think are necessary.
You should get something like this:
const { DataTypes } = require('sequelize');
const sequelize = require('./index');
const Product = sequelize.define('Product', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(200),
allowNull: false
},
description: {
type: DataTypes.TEXT
},
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
stock: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
module.exports = Product;
Open the file that contains a model, type the following prompt and let Copilot do the rest:
Add fields for create date and updated date and any restrictions you think are necessary.
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///ecommerce.db'
db = SQLAlchemy(app)
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
products = db.relationship('Product', backref='category', lazy=True)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text)
price = db.Column(db.Float, nullable=False)
stock = db.Column(db.Integer, nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
class Customer(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
name = db.Column(db.String(100), nullable=False)
address = db.Column(db.String(200))
phone = db.Column(db.String(20))
orders = db.relationship('Order', backref='customer', lazy=True)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), nullable=False)
status = db.Column(db.String(50), default='pending')
total_amount = db.Column(db.Float, nullable=False)
payment = db.relationship('Payment', backref='order', uselist=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
class Payment(db.Model):
id = db.Column(db.Integer, primary_key=True)
order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False)
amount = db.Column(db.Float, nullable=False)
payment_method = db.Column(db.String(50), nullable=False)
status = db.Column(db.String(50), default='pending')
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
@app.route('/products', methods=['GET'])
def get_products():
products = Product.query.all()
return jsonify([{
'id': p.id,
'name': p.name,
'price': p.price,
'stock': p.stock,
'category_id': p.category_id,
'created_at': p.created_at,
'updated_at': p.updated_at
} for p in products])
@app.route('/products', methods=['POST'])
def create_product():
data = request.get_json()
product = Product(
name=data['name'],
description=data.get('description'),
price=data['price'],
stock=data['stock'],
category_id=data['category_id']
)
db.session.add(product)
db.session.commit()
return jsonify({
'id': product.id,
'created_at': product.created_at,
'message': 'Product created successfully'
}), 201
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
Open the file that contains a model, type the following prompt and let Copilot do the rest:
Add fields for create date and updated date and any restrictions you think are necessary.
using System.ComponentModel.DataAnnotations;
namespace ECommerceApi.Models
{
public class Category
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
public string Description { get; set; }
[Required]
[DataType(DataType.DateTime)]
public DateTime CreatedDate { get; set; }
[DataType(DataType.DateTime)]
public DateTime? UpdatedDate { get; set; }
public ICollection<Product> Products { get; set; }
}
}